package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.common.redis.cache.GuiguCache;
import com.spzx.product.api.domain.SkuQuery;
import com.spzx.product.api.domain.vo.SkuLockVo;
import com.spzx.product.api.domain.vo.SkuPrice;
import com.spzx.product.api.domain.vo.SkuStockVo;
import com.spzx.product.api.domain.Product;
import com.spzx.product.api.domain.ProductDetails;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.domain.SkuStock;
import com.spzx.product.mapper.ProductDetailsMapper;
import com.spzx.product.mapper.ProductMapper;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.service.IProductService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 商品Service业务层处理
 */
@Slf4j
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    ProductMapper productMapper;

    @Autowired
    ProductSkuMapper productSkuMapper;

    @Autowired
    ProductDetailsMapper productDetailsMapper;

    @Autowired
    SkuStockMapper skuStockMapper;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    /**
     * 查询商品列表
     *
     * @param product 商品
     * @return 商品
     */
    @Override
    public List<Product> selectProductList(Product product) {
        return productMapper.selectProductList(product);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int insertProduct(Product product) {
        //1.保存SPU
        productMapper.insert(product); //主键回填
        Long productId = product.getId();


        List<ProductSku> productSkuList = product.getProductSkuList();
        int size = productSkuList.size();
        for (int i = 0; i< size; i++) {
            //2.保存多个SKU
            ProductSku productSku = productSkuList.get(i);
            productSku.setSkuCode(productId+"_"+i);
            productSku.setSkuName(product.getName()+" "+productSku.getSkuSpec());
            productSku.setProductId(productId);
            productSku.setStatus(0);
            productSkuMapper.insert(productSku); //主键回填

            //3.保存库存
            SkuStock skuStock = new SkuStock();
            skuStock.setSkuId(productSku.getId());
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setLockNum(0);
            skuStock.setAvailableNum(productSku.getStockNum());
            skuStock.setSaleNum(0);
            skuStock.setStatus(0);
            skuStockMapper.insert(skuStock);
        }

        //4.保存详情图片
        ProductDetails productDetails = new ProductDetails();
        productDetails.setProductId(productId);
        productDetails.setImageUrls(String.join(",",product.getDetailsImageUrlList()));
        productDetailsMapper.insert(productDetails);

        return 1;
    }


    @Override
    public Product selectProductById(Long id) {
        //1.查询商品
        Product product = productMapper.selectById(id);
        if(product==null){
            throw new ServiceException("数据不存在!");
        }

        //2.商品sku
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, id));

        //2.1假设：根据6个sku可以获取6个skuId,可以获取6个SkuStock对象；
        //获取每一个skuStock库存值，封装在sku对象中。

        List<Long> skuIdList = productSkuList.stream().map(ProductSku::getId).toList();
        //3.查询库存
        List<SkuStock> skuStockList = skuStockMapper.selectList(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId, skuIdList));
        Map<Long, Integer> skuIdToTotalNumMap = skuStockList.stream().collect(Collectors.toMap(SkuStock::getSkuId, SkuStock::getTotalNum));

        for (ProductSku productSku : productSkuList) {
            productSku.setStockNum(skuIdToTotalNumMap.get(productSku.getId()));
        }
        product.setProductSkuList(productSkuList);

        //4.查询详情图片
        ProductDetails productDetails = productDetailsMapper
                .selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId,id));
        product.setDetailsImageUrlList(Arrays.asList(productDetails.getImageUrls().split(",")));

        return product;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int updateProduct(Product product) {
        //1.修改SPU
        productMapper.updateById(product);


        List<ProductSku> productSkuList = product.getProductSkuList();
        for (ProductSku productSku : productSkuList) {
            //2.修改多个SKU
            productSku.setSkuName(product.getName()+" "+productSku.getSkuSpec());
            productSkuMapper.updateById(productSku);

            //3.修改多个SkuStock
            SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId,productSku.getId()));
            skuStock.setTotalNum(productSku.getStockNum());
            skuStock.setAvailableNum(productSku.getStockNum() - skuStock.getLockNum());
            skuStockMapper.updateById(skuStock);
        }

        //4.修改详情图片信息
        ProductDetails productDetails = new ProductDetails();
        productDetails.setImageUrls(String.join(",",product.getDetailsImageUrlList()));
        productDetailsMapper.update(productDetails,new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId,product.getId()));

        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int deleteProductByIds(Long[] ids) {
        //1.删除Product
        productMapper.deleteBatchIds(Arrays.asList(ids)); //  where id in(1,2,3)

        //2. 删除ProductSku
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getProductId, ids));

        List<Long> skuIdList = productSkuList.stream().map(ProductSku::getId).toList();

        productSkuMapper.deleteBatchIds(skuIdList);

        //3.删除SkuStock
        skuStockMapper.delete(new LambdaQueryWrapper<SkuStock>().in(SkuStock::getSkuId,skuIdList));

        //4.删除ProductDetails
        productDetailsMapper.delete(new LambdaQueryWrapper<ProductDetails>().in(ProductDetails::getProductId,ids));

        return 1;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateAuditStatus(Long id, Integer auditStatus) {
        Product product = new Product();
        product.setId(id);
        if(auditStatus == 1) {
            product.setAuditStatus(1);
            product.setAuditMessage("审批通过");
        } else {
            product.setAuditStatus(-1);
            product.setAuditMessage("审批不通过");
        }
        productMapper.updateById(product);
    }


    @Override
    public void updateStatus(Long id, Integer status) {
        Product product = new Product();
        product.setId(id);
        if(status == 1) {
            product.setStatus(1); //上架
            RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("sku:bloom:filter");
            List<ProductSku> productSkuList = productSkuMapper
                    .selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId,id));
            for (ProductSku productSku : productSkuList) {
                bloomFilter.add(productSku.getId()); //存放数据时，布隆过滤器会使用多个hash函数计算二级制数组索引位置，然后将其标记成1.
            }
        } else {
            product.setStatus(-1); //下架
        }
        productMapper.updateById(product);
    }


    @Override
    public List<ProductSku> getTopSale() {
        return productSkuMapper.getTopSale();
    }


    @Override
    public List<ProductSku> selectProductSkuList(SkuQuery skuQuery) {
        return productSkuMapper.selectProductSkuList(skuQuery);
    }

    //======以下是详情查询的接口======================================================================


    /*@Override
    public ProductSku getProductSku(Long skuId) {
        //1.先查询redis缓存，有，直接返回，
        String dataKey = "product:sku:" + skuId;

        ProductSku productSku = null;
        if(redisTemplate.hasKey(dataKey)){
            productSku = (ProductSku)redisTemplate.opsForValue().get(dataKey);
            log.info("命中缓存，直接返回，线程ID：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
            return productSku;
        }

        //2.没有,再查询数据库，放在缓存中，给下次访问使用。利用缓存提高效率。
        String lockKey = "product:sku:lock:" + skuId;
        String lockVal = UUID.randomUUID().toString().replaceAll("-","");
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);
        if(ifAbsent){ //加分布式锁成功
            try {
                log.info("获取锁成功：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
                productSku = productSkuMapper.selectById(skuId);
                long ttl = productSku == null ? 1 * 60 : 10 * 60; //解决缓存穿透：解决方案1（null值也存储，但是时间短一些。）
                redisTemplate.opsForValue().set(dataKey,productSku,ttl,TimeUnit.SECONDS);
                return productSku;
            } finally {
                //4.业务执行完毕释放锁
                String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(scriptText);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
            }
        }else{ //加分布式锁失败,等待自旋
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.error("获取锁失败，自旋：{}，线程名称：{}", Thread.currentThread().getId(), Thread.currentThread().getName());
            return getProductSku(skuId);
        }
    }*/


    @GuiguCache(prefix = "product:sku:")
    @Override
    public ProductSku getProductSku(Long skuId) {
        ProductSku productSku = productSkuMapper.selectById(skuId);
        return productSku;
    }

    @GuiguCache(prefix = "product:")
    @Override
    public Product getProduct(Long id) {
        return productMapper.selectById(id);
    }

    @GuiguCache(prefix = "skuPrice:")
    @Override
    public SkuPrice getSkuPrice(Long skuId) {
        ProductSku productSku = productSkuMapper.selectById(skuId);
        SkuPrice skuPrice = new SkuPrice();
        skuPrice.setSkuId(skuId);
        skuPrice.setSalePrice(productSku.getSalePrice());
        skuPrice.setMarketPrice(productSku.getMarketPrice());
        return skuPrice;
    }

    @GuiguCache(prefix = "productDetails:")
    @Override
    public ProductDetails getProductDetails(Long productId) {
        ProductDetails productDetails = productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductDetails>().eq(ProductDetails::getProductId, productId));
        return productDetails;
    }

    @GuiguCache(prefix = "skuSpecValue:")
    @Override
    public Map<String, Long> getSkuSpecValue(Long productId) {
        Map<String, Long> map = new HashMap<>();
        List<ProductSku> productSkuList = productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().eq(ProductSku::getProductId, productId));
        for (ProductSku productSku : productSkuList) {
            map.put(productSku.getSkuSpec(),productSku.getId());
        }
        return map;
    }

    @GuiguCache(prefix = "skuStockVo:")
    @Override
    public SkuStockVo getSkuStock(Long skuId) {
        SkuStock skuStock = skuStockMapper.selectOne(new LambdaQueryWrapper<SkuStock>().eq(SkuStock::getSkuId, skuId));
        SkuStockVo vo = new SkuStockVo();
        vo.setSkuId(skuId);
        vo.setAvailableNum(skuStock.getAvailableNum());
        vo.setSaleNum(skuStock.getSaleNum());
        return vo;
    }


    @Override
    public List<SkuPrice> getSkuPriceList(List<Long> skuIdList) {
        //select id,sale_price,market_price from product_sku where id in (1,4,8)
        List<ProductSku> productSkuList =
                productSkuMapper.selectList(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getId, skuIdList)
                        .select(ProductSku::getId,ProductSku::getSalePrice,ProductSku::getMarketPrice));
        //类型转换
         return productSkuList.stream().map(item -> {
            SkuPrice skuPrice = new SkuPrice();
            skuPrice.setSkuId(item.getId());
            skuPrice.setSalePrice(item.getSalePrice());
            skuPrice.setMarketPrice(item.getMarketPrice());
            return skuPrice;
        }).toList();
    }

    @Transactional
    @Override
    public String checkAndLock(String orderNo, List<SkuLockVo> skuLockVoList) {
        String dataKey = "sku:lock:data:" + orderNo; //用于存储锁定库存数据
        String lockKey = "sku:checkAndLock:lock:"+orderNo;
        //去重处理
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, orderNo, 1, TimeUnit.HOURS);
        if(!ifAbsent){
            if(redisTemplate.hasKey(dataKey)){
                return "";
            }else{
                return "请勿重复提交";
            }
        }

        //遍历所有商品，对每一个商品进行库存检查(增加行锁 for update,不允许别的事务进行数据操作。)
        for (SkuLockVo skuLockVo : skuLockVoList) {
            SkuStock skuStock = skuStockMapper.check(skuLockVo.getSkuId(),skuLockVo.getSkuNum());
            if(skuStock == null){ //库存不够或数据不存在
                skuLockVo.setIsHaveStock(false);
            }else{
                skuLockVo.setIsHaveStock(true);
            }
        }
        
        //判断是否存在库存不够的商品，如果存在库存不够的商品情况，那么，返回特定字符串消息即可；
        if(skuLockVoList.stream().anyMatch(skuLockVo->!skuLockVo.getIsHaveStock())){ //不能买，抛异常。
            StringBuffer result = new StringBuffer();
            List<SkuLockVo> noHaveskuLockVoList = skuLockVoList.stream().filter(skuLockVo -> !skuLockVo.getIsHaveStock()).toList();
            for (SkuLockVo skuLockVo : noHaveskuLockVoList) {
                result.append("商品：["+skuLockVo.getSkuId()+"]库存不足;"); //如果希望拼接商品名称，调用   ProductSku = getBySkuId(skuId)
            }
            redisTemplate.delete(lockKey);
            return result.toString();
        }else{
            //如果所有库存检查没问题，商品库存都够。那么才会锁定库存，不让别人买这个商品。
            for (SkuLockVo skuLockVo : skuLockVoList) {
                int row = skuStockMapper.lock(skuLockVo.getSkuId(),skuLockVo.getSkuNum()); //减可用库存，增加锁定库存
                if(row == 0){
                    redisTemplate.delete(lockKey);
                    throw new ServiceException("锁库存失败");
                }
            }
        }

        //把锁定库存信息保存到redis中，不设置过期时间，等着  解锁库存(取消订单)  或   减库存(支付成功)
        redisTemplate.opsForValue().set(dataKey,skuLockVoList);
        return "";
    }


    @Override
    public void unlock(String orderNo) {
        String dataKey = "sku:lock:data:" + orderNo; //用于获取存储锁定库存数据，与存储时用的key一致。
        String lockKey = "sku:unlock:lock:"+orderNo;
        //1.去重
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, orderNo, 1, TimeUnit.HOURS);
        if(!ifAbsent){
            //throw new ServiceException("请勿重复操作");
            return;
        }

        //2.数据不在
        List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)redisTemplate.opsForValue().get(dataKey);
        if(CollectionUtils.isEmpty(skuLockVoList)){ // 可能已经支付成功后删除了缓存。
            redisTemplate.delete(lockKey);
            return;
        }
        
        //3.数据在
        for (SkuLockVo skuLockVo : skuLockVoList) {
            int row = skuStockMapper.unlock(skuLockVo.getSkuId(),skuLockVo.getSkuNum()); //解锁库存，锁定库存逆操作。
            if(row==0){
                redisTemplate.delete(lockKey);
                throw new ServiceException("解锁失败");
            }
        }

        //4.活干完后删除缓存数据; 避免重复解锁 或者 重复减库存
        redisTemplate.delete(dataKey);
    }

    @Override
    public void minus(String orderNo) {
        String dataKey = "sku:lock:data:" + orderNo; //用于获取存储锁定库存数据，与存储时用的key一致。
        String lockKey = "sku:minus:lock:"+orderNo;
        //1.去重
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, orderNo, 1, TimeUnit.HOURS);
        if(!ifAbsent){
            //throw new ServiceException("请勿重复操作");
            return;
        }

        //2.数据不在
        List<SkuLockVo> skuLockVoList = (List<SkuLockVo>)redisTemplate.opsForValue().get(dataKey);
        if(CollectionUtils.isEmpty(skuLockVoList)){ // 可能已经支付成功后删除了缓存。
            redisTemplate.delete(lockKey);
            return;
        }

        //3.数据在
        for (SkuLockVo skuLockVo : skuLockVoList) {
            int row = skuStockMapper.minus(skuLockVo.getSkuId(),skuLockVo.getSkuNum()); //减库存
            if(row==0){
                redisTemplate.delete(lockKey);
                throw new ServiceException("减库存失败");
            }
        }

        //4.活干完后删除缓存数据; 避免重复解锁 或者 重复减库存
        redisTemplate.delete(dataKey);
    }
}